#pragma once

#include <cstdint>
#include <fstream>
#include <vector>

#pragma pack(push, 1)
struct TGAHeader {
	uint8_t  idlength = 0;
	uint8_t  colormaptype = 0;
	uint8_t  datatypecode = 0;
	uint16_t colormaporigin = 0;
	uint16_t colormaplength = 0;
	uint8_t  colormapdepth = 0;
	uint16_t x_origin = 0;
	uint16_t y_origin = 0;
	uint16_t width = 0;
	uint16_t height = 0;
	uint8_t  bitsperpixel = 0;
	uint8_t  imagedescriptor = 0;
};
#pragma pack(pop)
static_assert(sizeof(TGAHeader) == 18, "wrong TGAHader format");

struct TGAColor {
	uint8_t bgra[4] = { 0, 0, 0, 0 }; // black. r,g,b,a ranges in [0, 255]
	uint8_t bytespp = { 0 };

	TGAColor() = default;
	TGAColor(const uint8_t R, const uint8_t G, const uint8_t B, const uint8_t A = 255)
		: bgra{ B, G, R, A } {}
	TGAColor(const uint8_t* p, const uint8_t bpp) : bytespp(bpp) {
		for (int i = bpp; i--; bgra[i] = p[i]) {
		}
	}
	uint8_t& operator[](const int i) { return bgra[i]; }

	TGAColor operator+(const TGAColor& color) const {
		return TGAColor(
			bgra[2] + color.bgra[2], 
			bgra[1] + color.bgra[1], 
			bgra[0] + color.bgra[0], 
			bgra[3] + color.bgra[3] 
		);
	}

	/// scalar multiplication
	/// \param t the constant of scalar multiplication, t should range in [0.0f, 1.0f]; or else, do not perform the operation
	TGAColor operator*(const float t) const {
		if (t > 1.0f || t < 0.0f)
			return *this;

		uint8_t newR = static_cast<uint8_t>(bgra[2] * t);
		uint8_t newG = static_cast<uint8_t>(bgra[1] * t);
		uint8_t newB = static_cast<uint8_t>(bgra[0] * t);
		uint8_t newA = static_cast<uint8_t>(bgra[3] * t);

		return TGAColor(
			newR,
			newG,
			newB,
			newA
		);
	}
};

/// linear interpolation of color
inline TGAColor ColorLerp(const TGAColor& color1, const TGAColor& color2, float t) {
	return color1 * (1 - t) + color2 * t;
}

struct TGAImage {
public:
	enum Format { GRAYSCALE = 1, RGB = 3, RGBA = 4 };

	TGAImage(int w, int h, int bpp);
	bool read_tga_file(const std::string& filename);
	bool write_tga_file(const std::string& filename, const bool vflip = true, const bool rle = true) const;
	void flip_horizontally();
	void flip_vertically();
	TGAColor get(int x, int y) const;
	void set(int x, int y, const TGAColor& c);
	int width() const;
	int height() const;

private:
	bool load_rle_data(std::ifstream& in);
	bool unload_rle_data(std::ofstream& out) const;

	int w = 0;
	int h = 0;
	int bpp = 0; // bytes per pixel
	std::vector<uint8_t> data{};
};